...loading
2024-12-19
타입스크립트는 자바스크립트의 동적 특성으로 인해 발생할 수 있는 런타임 타입 오류를 사전에 방지하기 위해 설계된 정적 타입 언어다.
자바스크립트는 런타임에 타입이 결정되므로, 타입 관련 오류가 발생했을 때 사용자가 이를 직접 마주할 가능성이 크다. 반면, 타입스크립트는 자바와 유사하게 컴파일 타임에 타입을 결정하기 때문에, 코드 작성 단계에서 오류를 빠르게 발견하고 수정할 수 있다. 이를 통해 함수가 의도한 방식 외의 호출이나 잘못된 데이터 타입 사용을 에러로 처리하며, 코드의 안정성을 높일 수 있다.
또한, 데이터의 타입을 명확히 정의하므로, 데이터 타입을 파악하기 위한 추가적인 시간과 노력을 절약할 수 있다. 이러한 이유로 타입스크립트는 대규모 프로젝트나 유지보수가 중요한 환경에서 개발 생산성과 코드 품질을 향상시키는데 도움을 준다.
타입스크립트는 다양한 기본 타입을 제공하여 변수, 배열, 함수, 그리고 열거형(enum) 등을 정의할 수 있다.
// number : 숫자형 데이터를 나타내며, 정수와 실수를 모두 포함한다. let age : number = 30; // boolean : 참 또는 거짓을 나타내는 논리형 데이터를 정의한다. let isAdult : boolean = true; // string: 문자나 문자열 데이터를 정의한다. let name: string = "Taki"; let a : number[] = [1,2,3]; let week : string[] = ['mon', tue', 'sat']; // tuple let b : [string, number]; b= ['a', 1]; // void : 아무것도 반환하지 않는 함수 function Hola(): void { console.log('Hola!'); } // never : 항상 에러를 반환하거나, 무한루프 함수에 정의 function showError() : never { throw new Error(); } // null, undefined let a : null = null; let b : undefined = undefined; // enum : 비슷한 값들끼리 묶는 용도; 다시 정리 enum Os { Window, Ios, Android }
타입스크립트에서 Interface는 객체의 설계도라 볼 수 있다. 타입스크립트에서 우리는 Interface를 통해 객체의 틀을 정의할 수 있고, 이러한 틀을 사용하여 안정적인 프로그래밍을 할 수 있다.
// 인터페이스명은 대문자로 짓는다. interface Car { ownerName : string; brand : string; productionYear : number; goForward() : void; } const myCar : Car { onwerName : "Kim", brand: "Ferrari", productionYear: 2074, goForward: () => console.log("부릉부릉"); } function searchCarInfo(theCar : Car) : void { console.log(`${theCar.ownerName}`님의 차량은 ${theCar.brand}, ${theCar.producitonYear}연식입니다.); } searchCarInfo(myCar); // Kim님의 차량은 Ferrari, 2074연식입니다.
위 코드는 Interface의 사용예시다. Interface를 사용하여 객체의 타입 정보를 명시할 수 있다. 또한 이를 통해 여러 데이터를 포함한 객체를 매개변수로 받는 함수를 안정적으로 운용할 수 있다.
클래스와 마찬가지로 interface는 Interface 간의 확장이 가능하며 다수의 확장을 허용한다.
interface Person { name: string; age: number; gender : string; } interface Student { major: string; } interface CollageStudent extends Person, Student { collageName : string; } const person: CollgeStudent = { name: 'Kim', age: 23, major: "Sociology", collageName: "ABC" };
함수의 타입은 Interface를 통해서도 정의할 수 있다.
interface isLoggedin { (username: string, accessToken: string): Promise<boolean>; } // 매개 변수 이름이 인터페이스와 일치할 필요가 없다. let loginCheck: isLoggedIn = async function(user, token) { ... // authentication logic ... return true; }
Interface를 통해 함수 오버로딩이 가능하다. 함수 오버로딩이란 서로 다른 타입의 매개변수들을 가진 동일한 이름의 함수를 중복하여 정의하는 것을 의미한다.
// 함수 타입으로 구현한 오버로딩 function add(a: number, b: number): number; function add(a: string, b: string): string; function add(a: number | string, b: number | string) : number | string { return a + b } // 인터페이스로 구현한 오버로딩 interface Add { (x: number, y: number): number; (x: string, y: string): string; } function add(a: number | string, b: number | string) : Add { return a + b } // 결과 const combineString : string = add("Taki", "Town"); // TakiTown const addNumber : number = add(5, 5);// 10
리터럴 타입은 간단한 개념이다. 리터럴 타입은 특정 값만 허용하도록 제한 하는 타입으로, 변수가 가질 수 있는 값의 범위를 좁히는데 사용된다. 예를 들어 "Hello"라는 문자열을 리터럴로 선언하면, 해당 타입을 지닌 데이터는 오직 "Hello"라는 문자열만을 값으로 가질 수 있다. 리터럴 타입은 문자열과 숫자형으로 정의할 수 있다.
// 문자열 리터럴 타입 type Fruit = "apple" | "orange" | "grape" | "cherry" | "mango"; const myFruit1 : Fruit = "mango"; const myFruit2 : Fruit = "melon"; // Error: Type 'melon' is not assignable to type 'Fruit'. // 숫자형 리터럴 타입 type Grade = 1 | 2 | 3; const student1: Grade = 1; const student2: Grade = 5; // ❌ Error: Type 5 is not assignable to type 'Greade'.
type Size = 'small' | 'medium' | 'large'; interface ButtonProps { label: string; size: Size; } const button: ButtonProps = { label: 'Click Me', size: 'medium', // }; const invalidButton: ButtonProps = { label: 'Click Me', size: 'extra-large', // ❌ Error: Type '"extra-large"' is not assignable to type 'Size'. };
리터럴 타입은 값의 범위를 제한하거나 코드의 의도를 명확히 표현하고 타입 안정성을 강화하는 데 유용하다. 이를 통해 코드에서 발생할 수 있는 런타임 오류를 줄이고, IDE의 자동완성 기능도 더 잘 활용할 수 있다.
유니온 타입은 타입스크립트에서 하나 이상의 타입을 허용하는 기능이다. 유니온 타입은 |(파이프) 기호를 사용하여 선언된다.
// 기본형 let value: string | number; value = "hello"; value = 42; value = true; // ❌ Error: Type 'boolean' is not assignable to type 'string | number'. // 함수의 매개변수에 여러 타입을 하용하고 싶을 때도 사용할 수 있다. function add ( a: number | string , b: number | string) : number | string { return a+b; }
유니온 타입은 타입 안정성을 유지시켜주고, 유연한 타입을 정의하는데 장점을 가진다. 하지만 과도하게 사용할 경우 각 타입에 맞는 처리를 따로 작성해야 하므로 코드가 복잡해질 수 있다.
교차타입은 두 개 이상의 타입을 조합하여 모든 타입의 특징을 동시에 만족해야하는 새로운 타입을 정의하는 기능이다. 교차 타입은 &(앤퍼센트) 기호를 사용하여 선언한다. 교차타입은 여러 타입의 속성을 결합해야할 때 사용할 수 있다.
type Person = { name: string; age: number }; type Employee = { employeeId: number; department: string }; type Staff = Person & Employee; const staffMember: Staff = { name: "Taki", age: 30, employeeId: 101, department: "Engineering", };
교차타입을 통해 객체를 확정하거나 결합할 수도 있다.
type CanFly = { fly: () => void }; type CanSwim = { swim: () => void }; type Bird = CanFly & CanSwim; const duck: Bird = { fly: () => console.log("Flying!"), swim: () => console.log("Swimming!"), }; duck.fly(); // Flying! duck.swim(); // Swimming!
// 교차타입 사용으로 인한 타입 충돌 type A = { value: string }; type B = { value: number }; type C = A & B; // const obj: C = { value: "string" }; // ❌ Error
교차타입을 무분별하게 사용하면 타입의 복잡성이 증가할 수 있으며, 속성이 충돌할 수 있다. 위의 코드 예시와 같이 동일한 속성이 다른 타입을 가지게 되는 경우가 발생할 수 있다. 따라서 교차 타입 설계 시 이러한 상황들에 주의해야한다.
제네릭은 여러 가지 타입에서 동작하는 코드를 생성하는데 사용된다. 다양한 타입에서 코드가 동작할 수 있도록 하여 재사용성을 높일 수 있다. 제네릭은 꺾쇠 괄호 <>
안에 타입 파라미터를 정의해서 사용한다.
// 제네릭 사용예시 function identity<T>(value: T): T { return value; } // 사용 시 구체적인 타입 지정 가능 const num = identity<number>(10); // T는 number const str = identity<string>('Hello'); // T는 string const bool = identity(true); // 타입 추론으로 T는 boolean
타입스크립트를 사용하다보면 여러가지 타입을 허용해야할 때 any타입을 사용하는 경우가 잦다. any 타입을 사용해도 개발자의 의도와 같은 함수가 동작한다. 그러나 타입스크립트는 any라는 타입에 대해 타입검사를 실시하지 않는다. 즉 정적 타입 검사를 포기하는 것이기에 타입스크립트의 가장 큰 장점 또한 포기하는 셈이다.
이러한 any타입을 사용하기 위한 대안으로 제네릭을 사용할 수 있다. 제네릭은 여러가지 타입을 허용하면서도 타입 검사를 실시하기에 타입스크립트의 장점을 살릴 수 있다.
// 제네릭 타입 예시 function logText<T>(text: T): T { return text; }
위의 예시와 같이 함수의 이름 뒤에 제네릭 타입 <T>를 추가한다. 그리고 함수의 인자와 반환값 뒤에 T라는 타입을 추가한다. 이처럼 제네릭을 사용하면, 타입스크립트가 함수의 입력값에 대한 타입과 출력 값에 대한 타입이 동일한지 검사할 수 있게 된다.
유틸리티 타입은 기존 타입을 변형하거나 재사용하기 위해 제공되는 내장 제네릭 타입이다. 기존의 인터페이스, 제네릭 등의 기본 문법으로 충분히 타입을 변환할 수 있지만 유틸리티 타입을 사용하면 더 간결하고 효율적으로 타입을 정의할 수 있다. 자주 사용하는 유틸리티 타입에 대해 알아보자.
: Partial<T>는 특정 타입의 부분 집합을 만족하는 타입을 정의할 수 있다. 즉 주어진 타입 T의 속성을 선택적으로 취하는 타입을 반환한다. 아래의 예시 처럼 일부 속성만 업데이트하는 객체를 다룰 때 주로 사용한다.
interface User { id: number; name: string; email: string; } type PartialUser = Partial<User>; const updateUser: PartialUser = { name: "Alice", }; // id와 email이 없어도 오류 없음
Pick<T, K>는 타입 T에서 특정 속성 K만 골라 새로운 타입을 만든다. 필요한 속성만 추출하여 타입을 제한적으로 사용하고 싶을 때 사용한다.
interface User { id: number; name: string; email: string; } type UserPreview = Pick<User, "name" | "email">; const user: UserPreview = { name: "Alice", email: "alice@example.com", };
Omit<T, K>는 타입 T에서 특정 속성 K를 제외한 새로운 타입을 만든다. 필요 없는 속성을 제외한 타입을 정의하고 싶을 때 사용한다.
interface User { id: number; name: string; email: string; } type UserWithoutEmail = Omit<User, "email">; const user: UserWithoutEmail = { id: 1, name: "Alice", }; // email이 없어도 오류 없음
Comments